﻿using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Shared.Method
{

    public class YcdTelnetClass
    {
        /// <summary>
        /// 标志符,代表是一个TELNET 指令
        /// </summary>
        readonly Char IAC = Convert.ToChar(255);
        /// <summary>
        /// 表示一方要求另一方使用，或者确认你希望另一方使用指定的选项。
        /// </summary>
        readonly Char DO = Convert.ToChar(253);
        /// <summary>
        /// 表示一方要求另一方停止使用，或者确认你不再希望另一方使用指定的选项。
        /// </summary>
        readonly Char DONT = Convert.ToChar(254);
        /// <summary>
        /// 表示希望开始使用或者确认所使用的是指定的选项。
        /// </summary>
        readonly Char WILL = Convert.ToChar(251);
        /// <summary>
        /// 表示拒绝使用或者继续使用指定的选项。
        /// </summary>
        readonly Char WONT = Convert.ToChar(252);

        /// <summary>
        /// 表示后面所跟的是对需要的选项的子谈判
        /// </summary>
        readonly Char SB = Convert.ToChar(250);

        /// <summary>
        /// 子谈判参数的结束
        /// </summary>
        readonly Char SE = Convert.ToChar(240);

        const Char IS = '0';

        const Char SEND = '1';

        const Char INFO = '2';

        const Char VAR = '0';

        const Char VALUE = '1';

        const Char ESC = '2';

        const Char USERVAR = '3';

        /// <summary>
        /// 流
        /// </summary>
        byte[] m_byBuff = new byte[100000];

        /// <summary>
        /// 收到的控制信息
        /// </summary>
        //private List<string> m_ListOptions = new List<string>();

        /// <summary>
        /// 存储准备发送的信息
        /// </summary>
        //string m_strResp;

        //服务端IP和端口
        private string _serverAddress = null;
        private int _serverPort = 0;

        //源IP和端口
        private string _sourceAdrress = null;
        private int _sourcePort = 0;

        //YCDTCPBASE
        private YcdBitTcpClient _ycdBitClient = null;

        //保存从服务器上获取的数据
        //private ConcurrentQueue<string> _receiveMsgQueue = new ConcurrentQueue<string>();

        ////接收队列
        //private ConcurrentQueue<byte[]> _receiveDataQueue = new ConcurrentQueue<byte[]>();

        ////发送队列
        ////private ConcurrentQueue<byte[]> _sendDataQueue = new ConcurrentQueue<byte[]>();

        ////接收处理线程
        //private Task _manageReceice = null;

        //发送线程
        //private Task _manageSend = null;

        //错误信息
        public string LastErrorMsg { get { lock (lockObj) return _lastError; } }
        private string _lastError;

        //正常信息
        public string LastMsg { get { lock (lockObj) return _lastMsg; } }
        private string _lastMsg;

        //锁定对象
        private object lockObj = new object();

        //private string _recvMsg = "";

        private Queue<string> _receiveMsgQueue = new Queue<string>();

        //锁定错误信息
        public YcdTelnetClass(string serverIp, string sourceIp, int sourcePort,int serverPort=23)
        {
            //服务器
            _serverAddress = serverIp;
            _serverPort = serverPort;

            //源端
            _sourceAdrress = sourceIp;
            _sourcePort = sourcePort;
        }

        /// <summary>        
        /// 启动socket 进行telnet操作        
        /// </summary>       
        public bool Connect()
        {
            try
            {
                //判断源IP/目的IP/源端口/目的端口是否被占用
                _ycdBitClient = new YcdBitTcpClient(_serverAddress, _serverPort, _sourceAdrress, _sourcePort);

                //获取数据事件
                _ycdBitClient.OnConnected += _ycdBitClient_OnConnected;
                _ycdBitClient.OnError += _ycdBitClient_OnError;
                _ycdBitClient.OnReceive += _ycdBitClient_OnReceive;


                //RunManageMessage();
                //开始整个线程
                _ycdBitClient.Start();

                //RunSendData();

                return true;
            }
            catch (Exception ex)
            {
                LogHelper.WriteErrLogTxt("YcdTelnetClass.Connect()", "连接错误", ex.ToString());
                return false;
            }
        }

        /// <summary>
        /// 获取数据
        /// </summary>
        /// <param name="data">数据</param>
        /// <param name="length">长度</param>
        private void _ycdBitClient_OnReceive(byte[] data, int length)
        {
            //将接收到的数据转个码,顺便转成string型
            //string sRecieved = Encoding.GetEncoding("utf-8").GetString(data, 0, length);

            //声明一个字符串,用来存储解析过的字符串
            string m_strLine = "";
            //遍历Socket接收到的字符

            /*
             * 此循环用来调整linux 和 windows在换行上标记的区别
             * 最后将调整好的字符赋予给 m_strLine
            */
            for (int i = 0; i < length; i++)
            {
                Char ch = Convert.ToChar(data[i]);
                switch (ch)
                {
                    case '\r':
                        m_strLine += Convert.ToString("\r\n");
                        break;
                    case '\n':
                        break;
                    default:
                        m_strLine += Convert.ToString(ch);
                        break;
                }
            }

            try
            {
                //获得转义后的字符串的长度
                int strLinelen = m_strLine.Length;
                //如果长度为零
                if (strLinelen == 0)
                {
                    //则返回"\r\n" 即回车换行
                    m_strLine = Convert.ToString("\r\n");
                }

                //建立一个流,把接收的信息(转换后的)存进 mToProcess 中
                Byte[] mToProcess = new Byte[strLinelen];
                for (int i = 0; i < strLinelen; i++)
                    mToProcess[i] = Convert.ToByte(m_strLine[i]);

                // Process the incoming data
                //对接收的信息进行处理,包括对传输过来的信息的参数的存取和
                string mOutText = ProcessOptions(mToProcess);

                // Respond to any incoming commands
                //接收完数据,处理完字符串数据等一系列事物之后,开始回发数据
                //RespondToOptions();
                //解析命令后返回 显示信息(即除掉了控制信息)
                if (mOutText != "")
                {
                    Console.Write(mOutText);
                    _receiveMsgQueue.Enqueue(mOutText);
                    //_recvMsg = string.Format("{0}{1}", _recvMsg, mOutText);
                }
            }
            catch (Exception ex)
            {
                throw new Exception("接收数据的时候出错了! " + ex.Message);
            }
        }

        private void _ycdBitClient_OnError(string obj)
        {
            lock (lockObj)
            {
                _lastError = obj;
            }
        }

        private void _ycdBitClient_OnConnected(string obj)
        {
            lock (lockObj)
            {
                _lastMsg = obj;
            }
        }

        public bool Close()
        {
            try
            {
                if (_ycdBitClient != null
                    && _ycdBitClient.ClientConnected())
                    _ycdBitClient.Stop();//关闭连接

                //_manageReceice?.Dispose();
                //_manageSend?.Dispose();

                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }


        #region Source
        /// <summary>
        ///  发送数据的函数
        /// </summary>

        private void RespondToOptions(List<string> m_ListOptions)
        {
            try
            {
                //声明一个字符串,来存储 接收到的参数
                string strOption;

                string m_strResp="";
                /*
                 * 此处的控制信息参数,是之前接受到信息之后保存的
                 * 例如 255   253   23   等等
                 * 具体参数的含义需要去查telnet 协议
                 */
                for (int i = 0; i < m_ListOptions.Count; i++)
                {
                    //获得一个控制信息参数
                    strOption = m_ListOptions[i];
                    //根据这个参数,进行处理
                    m_strResp+=ArrangeReply(strOption);
                }

                DispatchMessage(m_strResp);
                m_strResp = "";
                //m_ListOptions.Clear();
            }
            catch (Exception ers)
            {
                Console.WriteLine("错错了,在回发数据的时候 " + ers.Message);
            }
        }

        /// <summary>
        /// 解析接收的数据,生成最终用户看到的有效文字,同时将附带的参数存储起来
        /// </summary>
        /// <param name="m_strLineToProcess">收到的处理后的数据</param>
        /// <returns></returns>
        private string ProcessOptions(byte[] m_strLineToProcess)
        {

            string m_DISPLAYTEXT = "";
            string m_strTemp = "";
            string m_strOption = "";
            string m_strNormalText = "";
            bool bScanDone = false;
            int ndx = 0;
            int ldx = 0;
            char ch;
            try
            {
                //存储指令信息
                List<string> m_ListOptions = new List<string>();

                //把数据从byte[] 转化成string
                for (int i = 0; i < m_strLineToProcess.Length; i++)
                {
                    char ss = Convert.ToChar(m_strLineToProcess[i]);
                    m_strTemp = m_strTemp + Convert.ToString(ss);
                }

                //此处意义为,当没描完数据前,执行扫描
                while (bScanDone != true)
                {
                    //获得长度
                    int lensmk = m_strTemp.Length;
                    //之后开始分析指令,因为每条指令为255 开头,故可以用此来区分出每条指令
                    ndx = m_strTemp.IndexOf(Convert.ToString(IAC));

                    //此处为出错判断,本无其他含义
                    if (ndx > lensmk)
                        ndx = m_strTemp.Length;

                    //此处为,如果搜寻到IAC标记的telnet 指令,则执行以下步骤
                    if (ndx != -1)
                    {
                        #region 如果存在IAC标志位
                        // 将 标志位IAC 的字符 赋值给最终显示文字 
                        m_DISPLAYTEXT += m_strTemp.Substring(0, ndx);
                        // 此处获得命令码
                        ch = m_strTemp[ndx + 1];

                        //如果命令码是253(DO) 254(DONT)  521(WILL) 252(WONT) 的情况下
                        if (ch == DO || ch == DONT || ch == WILL || ch == WONT)
                        {
                            //将以IAC 开头3个字符组成的整个命令存储起来
                            m_strOption = m_strTemp.Substring(ndx, 3);
                            m_ListOptions.Add(m_strOption);

                            // 将 标志位IAC 的字符 赋值给最终显示文字 
                            m_DISPLAYTEXT += m_strTemp.Substring(0, ndx);

                            //将处理过的字符串删去
                            string txt = m_strTemp.Substring(ndx + 3);
                            m_strTemp = txt;
                        }
                        //如果IAC后面又跟了个IAC (255)
                        else if (ch == IAC)
                        {
                            //则显示从输入的字符串头开始,到之前的IAC 结束
                            m_DISPLAYTEXT = m_strTemp.Substring(0, ndx);
                            //之后将处理过的字符串排除出去
                            m_strTemp = m_strTemp.Substring(ndx + 1);
                        }
                        //如果IAC后面跟的是SB(250)
                        else if (ch == SB)
                        {
                            m_DISPLAYTEXT = m_strTemp.Substring(0, ndx);
                            ldx = m_strTemp.IndexOf(Convert.ToString(SE));
                            m_strOption = m_strTemp.Substring(ndx, ldx);
                            m_ListOptions.Add(m_strOption);
                            m_strTemp = m_strTemp.Substring(ldx);
                        }

                        #endregion
                    }
                    //若字符串里已经没有IAC标志位了
                    else
                    {
                        //显示信息累加上m_strTemp存储的字段
                        m_DISPLAYTEXT = m_DISPLAYTEXT + m_strTemp;
                        bScanDone = true;
                    }
                }
                //输出人看到的信息
                m_strNormalText = m_DISPLAYTEXT;

                //处理消息回应
                RespondToOptions(m_ListOptions);

            }
            catch (Exception eP)
            {
                throw new Exception("解析传入的字符串错误:" + eP.Message);
            }
            return m_strNormalText;

        }

        //解析传过来的参数,生成回发的数据到m_strResp
        private string ArrangeReply(string strOption)
        {
            try
            {
                string m_strResp = null;

                Char Verb;
                Char Option;
                Char Modifier;
                Char ch;
                bool bDefined = false;
                //排错选项,无啥意义
                if (strOption.Length < 3) return "";

                //获得命令码
                Verb = strOption[1];
                //获得选项码
                Option = strOption[2];

                //如果选项码为 回显(1) 或者是抑制继续进行(3)
                if (Option == 1 || Option == 3)
                {
                    bDefined = true;
                }
                // 设置回发消息,首先为标志位255
                m_strResp += IAC;

                //如果选项码为 回显(1) 或者是抑制继续进行(3) ==true
                if (bDefined == true)
                {
                    #region 继续判断
                    //如果命令码为253 (DO)
                    if (Verb == DO)
                    {
                        //我设置我应答的命令码为 251(WILL) 即为支持 回显或抑制继续进行
                        ch = WILL;
                        m_strResp += ch;
                        m_strResp += Option;
                    }
                    //如果命令码为 254(DONT)
                    if (Verb == DONT)
                    {
                        //我设置我应答的命令码为 252(WONT) 即为我也会"拒绝启动" 回显或抑制继续进行
                        ch = WONT;
                        m_strResp += ch;
                        m_strResp += Option;
                    }
                    //如果命令码为251(WILL)
                    if (Verb == WILL)
                    {
                        //我设置我应答的命令码为 253(DO) 即为我认可你使用回显或抑制继续进行
                        ch = DO;
                        m_strResp += ch;
                        m_strResp += Option;
                        //break;
                    }
                    //如果接受到的命令码为251(WONT) 
                    if (Verb == WONT)
                    {
                        //应答  我也拒绝选项请求回显或抑制继续进行
                        ch = DONT;
                        m_strResp += ch;
                        m_strResp += Option;
                        //    break;
                    }
                    //如果接受到250(sb,标志子选项开始)
                    if (Verb == SB)
                    {
                        /*
                         * 因为启动了子标志位,命令长度扩展到了4字节,
                         * 取最后一个标志字节为选项码
                         * 如果这个选项码字节为1(send)
                         * 则回发为 250(SB子选项开始) + 获取的第二个字节 + 0(is) + 255(标志位IAC) + 240(SE子选项结束)
                        */
                        Modifier = strOption[3];
                        if (Modifier == SEND)
                        {
                            ch = SB;
                            m_strResp += ch;
                            m_strResp += Option;
                            m_strResp += IS;
                            m_strResp += IAC;
                            m_strResp += SE;
                        }
                    }
                    #endregion
                }
                else //如果选项码不是1 或者3 
                {
                    #region 底下一系列代表,无论你发那种请求,我都不干
                    if (Verb == DO)
                    {
                        ch = WONT;
                        m_strResp += ch;
                        m_strResp += Option;
                    }
                    if (Verb == DONT)
                    {
                        ch = WONT;
                        m_strResp += ch;
                        m_strResp += Option;
                    }
                    if (Verb == WILL)
                    {
                        ch = DONT;
                        m_strResp += ch;
                        m_strResp += Option;
                    }
                    if (Verb == WONT)
                    {
                        ch = DONT;
                        m_strResp += ch;
                        m_strResp += Option;
                    }
                    #endregion
                }

                return m_strResp;
            }
            catch (Exception eeeee)
            {

                throw new Exception("解析参数时出错:" + eeeee.Message);
            }

        }

        /// <summary>
        /// 将信息转化成charp[] 流的形式,使用socket 进行发出
        /// 发出结束之后,使用一个匿名委托,进行接收,
        /// 之后这个委托里,又有个委托,意思是接受完了之后执行OnRecieveData 方法
        /// 
        /// </summary>
        /// <param name="strText"></param>
        void DispatchMessage(string strText)
        {
            try
            {
                //申请一个与字符串相当长度的char流
                Byte[] smk = new Byte[strText.Length];
                for (int i = 0; i < strText.Length; i++)
                {
                    //解析字符串,将其存储到char流中去
                    Byte ss = Convert.ToByte(strText[i]);
                    smk[i] = ss;
                }

                _ycdBitClient?.SendData(smk, strText.Length);

            }
            catch (Exception ers)
            {
                Console.WriteLine("出错了,在回发数据的时候:" + ers.Message);
            }
        }
        #endregion

        /// <summary>
        /// 返回客户端连接情况
        /// </summary>
        /// <returns></returns>
        public bool IsConnected()
        {
            return _ycdBitClient.ClientConnected();
        }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="message"></param>
        public void Send(string message)
        {
            DispatchMessage(message);
            DispatchMessage("\r\n");
        }

        /// <summary>
        /// 获取数据
        /// </summary>
        /// <param name="strKey"></param>
        /// <returns></returns>
        public bool GetReturnMsg(string strKey,out string allMsg)
        {
            allMsg = "";

            try
            {
                bool hasKey = false;

                for (int i = 0; i < 30; i++)
                {
                    string msg = "";

                    //尝试PING一下主板，失败返回FALSE

                    if(PingBaseClass.PingTest(_sourceAdrress, _serverAddress) == false)
                    {
                        msg = "与光猫的连接已经断开";
                        break;
                    }

                    Thread.Sleep(500);

                    while (_receiveMsgQueue.Count > 0 && (msg = _receiveMsgQueue.Dequeue()) != null)
                    {
                        allMsg = string.Format("{0}{1}", allMsg, msg.Replace("\0",""));//清除结束字符
                        //Thread.Sleep(40);
                    }

                    if (allMsg.ToUpper().Contains(strKey.ToUpper()))
                    {
                        hasKey = true;
                        break;
                    }
                }

                return hasKey;

            }
            catch (Exception ex)
            {
                lock (lockObj)
                    _lastError = ex.Message;

                return false;
            }
        }
    }
}
